package com.zengqingfa.springboot.redis.core;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @fileName: RedisLock
 * @author: zengqf3
 * @date: 2021-3-5 16:58
 * @description:
 */
@Component
public class RedisLock {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 版本一:setnx和expire是分开的两步操作，不具有原子性，
     * 如果执行完第一条指令应用异常或者重启了，锁将无法过期。
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public boolean tryLock(String key, String value, int timeout) {
        try {
            redisTemplate.opsForValue().set(key, value);
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 版本三:使用命令 SET key value[EX seconds][PX milliseconds][NX|XX]
     * EX seconds: 设定过期时间，单位为秒
     * PX milliseconds: 设定过期时间，单位为毫秒
     * NX: 仅当key不存在时设置值
     * XX: 仅当key存在时设置值
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public boolean tryLockWithSet(String key, String value, int timeout) {
        boolean flag = redisTemplate.opsForValue().setIfPresent(key, value, timeout, TimeUnit.SECONDS);
        return flag;
    }

    /**
     * 释放锁
     *
     * @param key
     */
    public boolean releaseLock2(String key, String value) {
        String val = (String) redisTemplate.opsForValue().get(key);
        if (Objects.equals(val, value)) {
            return redisTemplate.delete(key);
        }
        return false;
    }


    /**
     * 版本二：使用lua脚本
     *
     * @param key
     * @param value
     * @param seconds
     * @return
     */
    public boolean tryLockWithLua(String key, String value, int seconds) {
        // 执行 lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        // 指定 lua 脚本
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redislock.lua")));
        // 指定返回类型
        redisScript.setResultType(Long.class);
        // 参数一：redisScript，参数二：key列表，参数三：arg（可多个）
        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), value, seconds);
        return Objects.equals(execute, 1L);
    }


    /**
     * 释放锁
     *
     * @param key
     * @param value
     * @return
     */
    public boolean releaseLockWithLua(String key, String value) {
        // 执行 lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        // 指定 lua 脚本
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redislockrelease.lua")));
        // 指定返回类型
        redisScript.setResultType(Long.class);
        // 参数一：redisScript，参数二：key列表，参数三：arg（可多个）
        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), value);
        return Objects.equals(execute, 1L);
    }


}
